Conversation
Signed-off-by: Joana Maia <jmaia@contractor.linuxfoundation.org>
There was a problem hiding this comment.
Pull request overview
Adds Intercom to the frontend and wires it into the auth lifecycle so the widget boots for authenticated users and shuts down on logout.
Changes:
- Added Intercom configuration (env + app config) including Auth0 claim keys.
- Introduced an Intercom utility for script loading + boot/update/shutdown.
- Hooked Intercom boot/shutdown into the authentication flow (login/logout).
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 7 comments.
| File | Description |
|---|---|
| frontend/src/utils/intercom/index.ts | New Intercom loader/boot/update/shutdown utility module. |
| frontend/src/modules/auth/store/auth.actions.ts | Boots Intercom after login and shuts it down on logout. |
| frontend/src/config.js | Adds intercom config (appId, api base, Auth0 claim keys). |
| frontend/.env.dist.local | Adds a distribution env var for Intercom app id. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Set JWT in intercomSettings before boot — required for identity verification | ||
| if (options.intercom_user_jwt) { | ||
| window.intercomSettings = window.intercomSettings || {}; | ||
| window.intercomSettings.intercom_user_jwt = options.intercom_user_jwt; | ||
| } |
There was a problem hiding this comment.
intercom_user_jwt is stripped out of the payload passed to Intercom('boot', ...). If identity verification relies on providing intercom_user_jwt in the boot call (as Intercom’s standard flow does), this can lead to users being booted without verification. Include intercom_user_jwt in the boot payload (or pass window.intercomSettings as the boot object after merging user fields) so verification is reliably applied.
| const { intercom_user_jwt: _jwt, ...bootOptions } = options; | ||
| window.Intercom('boot', { | ||
| api_base: config.intercom.apiBase, | ||
| app_id: config.intercom.appId, | ||
| ...bootOptions, |
There was a problem hiding this comment.
intercom_user_jwt is stripped out of the payload passed to Intercom('boot', ...). If identity verification relies on providing intercom_user_jwt in the boot call (as Intercom’s standard flow does), this can lead to users being booted without verification. Include intercom_user_jwt in the boot payload (or pass window.intercomSettings as the boot object after merging user fields) so verification is reliably applied.
| const { intercom_user_jwt: _jwt, ...bootOptions } = options; | |
| window.Intercom('boot', { | |
| api_base: config.intercom.apiBase, | |
| app_id: config.intercom.appId, | |
| ...bootOptions, | |
| window.Intercom('boot', { | |
| api_base: config.intercom.apiBase, | |
| app_id: config.intercom.appId, | |
| ...options, |
| if (!config.intercom.appId) { | ||
| console.info('Intercom: Disabled (no appId configured)'); | ||
| reject(new Error('No Intercom app ID configured')); | ||
| return; | ||
| } |
There was a problem hiding this comment.
Treating “Intercom disabled by configuration” as a rejected Promise forces callers to handle this as an error path, even though it’s an expected state. Prefer resolving as a no-op when appId is missing (and/or exposing an isEnabled() helper) so downstream auth flows don’t need to special-case this.
| script.onerror = (error) => { | ||
| isLoading = false; | ||
| console.error('Intercom: Failed to load script', error); | ||
| }; |
There was a problem hiding this comment.
If the script fails quickly (onerror), boot() will still poll for up to 10s before rejecting, which delays auth lifecycle completion and adds avoidable background work. Consider making loadScript() return a shared Promise (cached across calls) that resolves on onload and rejects on onerror, and have boot() await that instead of polling; this will fail fast, simplify control flow, and avoid repeated intervals/timeouts for concurrent boots.
| const checkLoaded = setInterval(() => { | ||
| if (isLoaded && window.Intercom) { | ||
| clearInterval(checkLoaded); | ||
| clearTimeout(timeoutHandle); | ||
|
|
||
| if (isBooted) { | ||
| const { intercom_user_jwt: _jwt, ...updateOptions } = options; | ||
| update(updateOptions); | ||
| resolve(); | ||
| return; | ||
| } | ||
|
|
||
| isBooted = true; | ||
| try { | ||
| const { intercom_user_jwt: _jwt, ...bootOptions } = options; | ||
| window.Intercom('boot', { | ||
| api_base: config.intercom.apiBase, | ||
| app_id: config.intercom.appId, | ||
| ...bootOptions, | ||
| }); | ||
| resolve(); | ||
| } catch (error) { | ||
| isBooted = false; | ||
| console.error('Intercom: Boot failed', error); | ||
| reject(error); | ||
| } | ||
| } | ||
| }, 100); | ||
|
|
||
| const timeoutHandle = setTimeout(() => { | ||
| clearInterval(checkLoaded); | ||
| if (!isBooted) { | ||
| isLoading = false; | ||
| reject(new Error('Intercom script failed to load')); | ||
| } | ||
| }, 10000); |
There was a problem hiding this comment.
If the script fails quickly (onerror), boot() will still poll for up to 10s before rejecting, which delays auth lifecycle completion and adds avoidable background work. Consider making loadScript() return a shared Promise (cached across calls) that resolves on onload and rejects on onerror, and have boot() await that instead of polling; this will fail fast, simplify control flow, and avoid repeated intervals/timeouts for concurrent boots.
| email: user.email, | ||
| intercom_user_jwt: intercomJwt, | ||
| }).catch((error: any) => { | ||
| console.error('Intercom: Boot failed', error); |
There was a problem hiding this comment.
Logging the raw error object can inadvertently include sensitive context (depending on how errors are produced/serialized), and this path is triggered during authentication. Prefer logging a sanitized message (e.g., error?.message) and avoid dumping full objects that might contain user identifiers or token-related info.
| console.error('Intercom: Boot failed', error); | |
| console.error('Intercom: Boot failed:', (error && error.message) ? error.message : String(error)); |
| VUE_APP_CONVERSATIONS_PUBLIC_URL=http://localhost:3000 | ||
| VUE_APP_NANGO_URL=http://localhost:3003 | ||
| VUE_APP_ENV=local | ||
| VUE_APP_INTERCOM_APP_ID=mxl90k6y |
There was a problem hiding this comment.
.env.dist.local is typically a template; committing a concrete app id can cause accidental usage against the wrong Intercom workspace in local/dev setups. Consider leaving this blank or using an obvious placeholder value (with a short comment) to encourage explicit configuration per environment.
| VUE_APP_INTERCOM_APP_ID=mxl90k6y | |
| # Set your Intercom app id for this environment (leave blank if not used). | |
| VUE_APP_INTERCOM_APP_ID= |
Signed-off-by: Joana Maia <jmaia@contractor.linuxfoundation.org>
89b6576 to
1276bbe
Compare
| console.error('Intercom: Shutdown failed', error); | ||
| } | ||
| } | ||
| }; |
There was a problem hiding this comment.
Shutdown doesn't cancel a pending boot operation
Low Severity
shutdown() only acts when isBooted is true, but if it's called while boot() is still waiting for the script to load (polling via setInterval), the pending interval is never cleared. Once the script finishes loading, the interval callback will still execute and boot Intercom with the previous user's credentials. Adding a cancellation flag (e.g., a cancelled boolean checked inside the interval) would close this lifecycle gap.
Additional Locations (1)
Signed-off-by: Joana Maia <jmaia@contractor.linuxfoundation.org>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 3 total unresolved issues (including 2 from previous reviews).
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
| api_base: config.intercom.apiBase, | ||
| app_id: config.intercom.appId, | ||
| ...bootOptions, | ||
| }); |
There was a problem hiding this comment.
JWT stripped from Intercom boot call breaks verification
High Severity
The intercom_user_jwt is explicitly destructured out and excluded from the options passed to window.Intercom('boot', ...). It's only set on window.intercomSettings, but Intercom's boot API call uses its own argument object — not window.intercomSettings — for identity verification. Per Intercom's SPA documentation, the JWT must be passed directly in the boot call for authenticated sessions to work. Without it, identity verification will silently fail and users won't get an authenticated Intercom experience.


This pull request introduces Intercom integration into the frontend application, enabling user-specific Intercom chat support based on authentication state. The main changes include configuration additions, dynamic script loading and lifecycle management for Intercom, and hooks into the authentication flow to boot and shut down Intercom as users log in or out.
Intercom Integration
appId, API base, and Auth0 claim keys for user identification. [1] [2] [3]frontend/src/utils/intercom/index.tsto handle dynamic loading, booting, updating, and shutdown of the Intercom widget, with support for JWT-based identity verification.Authentication Flow Updates
Note
Medium Risk
Medium risk because it introduces a third-party script load and session-coupled boot/shutdown using Auth0 claims/JWTs; misconfiguration could impact login flows or leak user identifiers to Intercom.
Overview
Adds an Intercom integration to the frontend, including a new
config.intercomsection (app id, API base, and Auth0 claim keys) and wiringVUE_APP_INTERCOM_APP_IDthrough local env defaults and the docker runtime env-var replacement.Hooks Intercom into the auth lifecycle: on authenticated header setup it boots Intercom with user identity +
intercom_user_jwt(from Auth0 claims), and on logout it shuts Intercom down. A newutils/intercommodule handles dynamic script injection, queued boot/update calls, timeouts, and cleanup of JWT settings.Written by Cursor Bugbot for commit 86c4374. This will update automatically on new commits. Configure here.